Chapter 13

Lists, Grids, and Dynamic Collections

Session 13

Learning Objectives

By the end of this chapter, you will be able to:

1

When to Use Which Widget

Choosing the right widget for displaying collections is crucial for performance and user experience.

ListView

Vertical or horizontal linear lists; use when items are arranged one after another.

GridView

Multi-column layouts for media, products, or tiles.

CustomScrollView + Slivers

Advanced, high-performance scroll effects and mixed content (collapsing app bars, varied item types).

Key point: Use builder constructors (ListView.builder, GridView.builder, SliverList/SliverGrid with delegates) for large or infinite lists to build items lazily.

2

ListView Patterns

ListView provides several constructors for different use cases.

ListView Constructors

  • ListView(children: [...]) — small, fixed-size lists.
  • ListView.builder(itemCount: n, itemBuilder: ...) — efficient for large lists.
  • ListView.separated — insert separators between items easily.

Example Patterns (Conceptual)

Simple builder:

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    final item = items[index];
    return ListTile(title: Text(item.title));
  },
)

Separated:

ListView.separated(
  itemCount: items.length,
  itemBuilder: ...,
  separatorBuilder: (context, index) => Divider(),
)

Key Points

  • Use const where possible for static children.
  • Provide keys (ValueKey or ObjectKey) for stateful list items that may reorder to preserve state.
3

GridView and Responsive Grids

GridView provides flexible layouts for displaying items in a grid format.

GridView Options

  • GridView.count — quick fixed column grids.
  • GridView.builder with SliverGridDelegateWithFixedCrossAxisCount or WithMaxCrossAxisExtent — flexible, performant grids.

Responsive Grid Example

Use maxCrossAxisExtent to let items adapt to available width:

GridView.builder(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200,
    mainAxisSpacing: 8,
    crossAxisSpacing: 8,
    childAspectRatio: 3/4,
  ),
  itemBuilder: ...
)
4

Keys and Preserving State

Keys are essential for maintaining state when lists change.

Using Keys

  • Use keys to preserve identity when reordering or updating lists.
  • ValueKey(item.id) is common when items have stable IDs.
  • Without keys, Flutter may rebuild and lose state (e.g., text field cursor position) when items move.

Example

ListTile(key: ValueKey(item.id), title: Text(item.name));
5

Heterogeneous Lists and Item Types

Lists often contain different types of items that require different widgets.

Handling Different Item Types

  • When lists contain different item widgets (headers, cards, ads), switch on item type in itemBuilder.
  • For Sliver-based lists, use SliverList with child delegates that produce different widgets.

Pattern

if (item is Header) return HeaderWidget(...);
if (item is Product) return ProductTile(...);
6

Swipe-to-Dismiss and Dismissible

Dismissible widgets enable intuitive deletion gestures.

Using Dismissible

Use Dismissible to allow swipe-to-delete with animation and background widgets. Provide a unique key and handle confirm/undo flows.

Example

Dismissible(
  key: ValueKey(item.id),
  background: Container(color: Colors.red, child: Icon(Icons.delete)),
  onDismissed: (direction) { cart.remove(item); },
  child: ListTile(title: Text(item.name)),
)

UX note: Support undo via SnackBar with action that restores item; avoid immediate permanent deletion without confirmation in critical flows.

7

ReorderableListView

ReorderableListView enables drag-and-drop reordering of list items.

Using ReorderableListView

Use ReorderableListView for drag-and-drop reordering. Maintain underlying list order and update state on onReorder(oldIndex, newIndex).

Important: Provide keys on children and ensure stable ids. Update models and persist new order if necessary.

8

Infinite Lists, Pagination, and Pull-to-Refresh

Pagination and refresh patterns are essential for handling large datasets.

Pagination Strategies

  • Offset/limit (load next page at end): Common and simple.
  • Cursor-based pagination: Robust for changing datasets.

Implementation

  • Detect scroll position with ScrollController and trigger loading when approaching the end.
  • Use RefreshIndicator to enable pull-to-refresh for manual reload.

Example Scroll Detection Pattern

_scrollController.addListener(() {
  if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
    // load next page
  }
});

Best Practices

  • Debounce or guard concurrent page loads with a isLoadingMore flag.
  • Show loading footer or skeleton while loading.
  • Handle empty results and end-of-list states to avoid repeated calls.
9

Slivers for Advanced Scrolling

Slivers provide powerful scrolling capabilities for complex UIs.

CustomScrollView with Slivers

CustomScrollView with SliverAppBar, SliverList, SliverGrid, SliverToBoxAdapter lets you mix different scrollable sections and create collapsing headers, sticky sections, and performant large lists. Use SliverChildBuilderDelegate for lazy child creation and provide keys as needed.

Use Case

Collapsing toolbar (SliverAppBar) + product grid below, with performant scrolling and pinned headers.

10

Performance and Memory Tips

Optimizing list performance is crucial for smooth user experience.

Performance Best Practices

  • Use builder variants to avoid building off-screen widgets.
  • For heavy list items, precompute or cache expensive subparts outside build (e.g., image decoding).
  • Use const and small leaf widgets to minimize rebuild cost.
  • Recycle image widgets with cached_network_image or similar to avoid re-downloading.
11

Binding Collections to State

Connecting lists to state management ensures reactive updates.

State Binding

  • Keep source-of-truth list in a provider/state notifier. UI subscribes and rebuilds only necessary parts (use selectors to avoid full-list rebuilds if only a badge count changes).
  • When modifying lists (add/remove/reorder), update the model then call notifyListeners or setState accordingly.

Example (Provider)

final items = context.watch().items;
ListView.builder(... itemCount: items.length ...)

Optimization

Use ListView.builder and item-level ChangeNotifier or ValueListenable for per-item state to avoid rebuilding the entire list on small changes.

12

Empty, Loading, and Error States

Providing feedback for different states improves user experience.

User Feedback States

  • Loading: Centered spinner or skeleton placeholders.
  • Empty: Friendly illustration and CTA.
  • Error: Retry action and clear error copy.

For paginated lists, show loading footer and preserve previously loaded items while loading more.

13

Accessibility and Touch Targets

Making lists accessible ensures all users can interact with them effectively.

Accessibility Best Practices

  • Ensure tappable list items meet minimum size and have proper semantics for screen readers.
  • Provide meaningful labels for each item and use ListTile semantics where appropriate.
14

Exercises

Practice what you've learned with these exercises:

1. Product list with grid and pagination

Build a grid of product cards that loads initial page, loads next page on scroll, and shows a loading footer. Include a pull-to-refresh to reload the first page.

2. Reorderable todo list with persistence

Create a ReorderableListView for todos. Persist order locally (SharedPreferences or local JSON file) and reload on app start.

3. Heterogeneous feed with Slivers

Build a CustomScrollView containing: a SliverAppBar, a SliverList of posts, a SliverToBoxAdapter ad banner, and a SliverGrid of recommendations.

4. Swipe-to-delete with undo

Implement a dismissible shopping list where swiping removes item and shows SnackBar with Undo action to restore it.

15

Session Assignment

Complete Exercises 1 and 4. Provide code, screenshots for loading/empty/error states, and a short README describing your pagination approach and how you avoided duplicate loads.